`timescale 1ns / 1ps

`include "data_width.vh"

module rd_edge_off #(parameter
    DST_ID_DWIDTH       = `DST_ID_DWIDTH,
    VERTEX_PIPE_NUM     = `VERTEX_PIPE_NUM,
    MEM_AWIDTH          = `MEM_AWIDTH,
    EDGE_OFF_DWIDTH     = `EDGE_OFF_DWIDTH,
    VERTEX_MASK_WIDTH   = `VERTEX_MASK_WIDTH
    ) (
        input                                                   clk,
        input                                                   front_rst,
        input [DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]         front_dst_id,
        input [VERTEX_PIPE_NUM - 1 : 0]                         front_dst_data_valid,
        input                                                   mem_full,
        input                                                   addr_rev_rd_plus_valid,
        input                                                   back_stage_vertex_full,

        input [DST_ID_DWIDTH - 1 : 0]                           front_dst_id_ed,
        input [EDGE_OFF_DWIDTH - 1 : 0]                         front_edge_off_ed,
        input [MEM_AWIDTH - 1 : 0]                              front_edge_off_addr_ed,
        input [MEM_AWIDTH - 1 : 0]                              front_edge_info_addr_ed,
        input [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]     front_mem_edge_ed,
        input                                                   front_para_valid,

        // para
        output                                                  rst,
        output [DST_ID_DWIDTH - 1 : 0]                          dst_id_ed,
        output [EDGE_OFF_DWIDTH - 1 : 0]                        edge_off_ed,
        output [MEM_AWIDTH - 1 : 0]                             edge_info_addr_ed,
        output [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]    mem_edge_ed,
        output                                                  para_valid,

        output                                                  buffer_full_vertex,
        output [MEM_AWIDTH - 1 : 0]                             rd_edge_off_addr,
        output                                                  rd_edge_off_valid,
        output [DST_ID_DWIDTH * VERTEX_PIPE_NUM - 1 : 0]        dst_id,
        output [VERTEX_PIPE_NUM - 1 : 0]                        dst_data_valid);

    wire any_front_dst_data_valid;
    wire addr_buffer_empty, addr_buffer_full;
    wire addr_rev_full;
    wire [VERTEX_PIPE_NUM - 1 : 0] dst_buffer_empty, dst_buffer_full;

    assign any_front_dst_data_valid = front_dst_data_valid[0];

    rd_edge_off_para_trans P (
        .clk                        (clk),
        .front_rst                  (front_rst),
        .front_dst_id_ed            (front_dst_id_ed),
        .front_edge_off_ed          (front_edge_off_ed),
        .front_edge_info_addr_ed    (front_edge_info_addr_ed),
        .front_mem_edge_ed          (front_mem_edge_ed),
        .front_para_valid           (front_para_valid),

        .rst                        (rst),
        .edge_off_ed                (edge_off_ed),
        .dst_id_ed                  (dst_id_ed),
        .edge_info_addr_ed          (edge_info_addr_ed),
        .mem_edge_ed                (mem_edge_ed),
        .para_valid                 (para_valid));

    rd_edge_off_addr_gen E0 (
        .clk(clk), .rst(front_rst),
        .mem_full(mem_full), .addr_rev_rd_plus_valid(addr_rev_rd_plus_valid),

        .front_edge_off_addr_ed(front_edge_off_addr_ed),
        .front_para_valid(front_para_valid),

        .buffer_empty(addr_buffer_empty), .buffer_full(addr_buffer_full),
        .buffer_rev_full(addr_rev_full),
        .rd_edge_off_addr(rd_edge_off_addr), .rd_edge_off_valid(rd_edge_off_valid));

    generate
        genvar i;
        for (i = 0; i < VERTEX_PIPE_NUM; i = i + 1) begin : M2_BLOCK_1
            rd_edge_off_vertex_single V (
                .clk(clk), .rst(front_rst),
                .front_dst_id(front_dst_id[(i + 1) * DST_ID_DWIDTH - 1 : i * DST_ID_DWIDTH]), .front_dst_data_valid(front_dst_data_valid[i]),
                .any_front_dst_data_valid(any_front_dst_data_valid), .back_stage_vertex_full(back_stage_vertex_full),

                .buffer_empty(dst_buffer_empty[i]), .buffer_full(dst_buffer_full[i]),
                .dst_id(dst_id[(i + 1) * DST_ID_DWIDTH - 1 : i * DST_ID_DWIDTH]), .dst_data_valid(dst_data_valid[i]));
        end
    endgenerate

    assign buffer_full_vertex = dst_buffer_full[0];

endmodule

module rd_edge_off_para_trans #(parameter
    EDGE_OFF_DWIDTH     = `EDGE_OFF_DWIDTH,
    MEM_AWIDTH          = `MEM_AWIDTH,
    DST_ID_DWIDTH       = `DST_ID_DWIDTH,
    VERTEX_MASK_WIDTH   = `VERTEX_MASK_WIDTH
    ) (
        input                                                       clk,
        input                                                       front_rst,
        input [DST_ID_DWIDTH - 1 : 0]                               front_dst_id_ed,
        input [EDGE_OFF_DWIDTH - 1 : 0]                             front_edge_off_ed,
        input [MEM_AWIDTH - 1 : 0]                                  front_edge_info_addr_ed,
        input [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]         front_mem_edge_ed,
        input                                                       front_para_valid,

        output reg                                                  rst,
        output reg [DST_ID_DWIDTH - 1 : 0]                          dst_id_ed,
        output reg [EDGE_OFF_DWIDTH - 1 : 0]                        edge_off_ed,
        output reg [MEM_AWIDTH - 1 : 0]                             edge_info_addr_ed,
        output reg [EDGE_OFF_DWIDTH - VERTEX_MASK_WIDTH - 1 : 0]    mem_edge_ed,
        output reg                                                  para_valid);
    
    // rst
    always @ (posedge clk) begin
        rst <= front_rst;
    end

    // parameter
    always @ (posedge clk) begin
        if (front_rst) begin
            edge_info_addr_ed   <= 0;
            edge_off_ed         <= 0;
            mem_edge_ed         <= 0;
            dst_id_ed           <= 0;
            para_valid          <= 1'b0;
        end
        else begin
            if (front_para_valid) begin
                dst_id_ed           <= front_dst_id_ed;
                edge_info_addr_ed   <= front_edge_info_addr_ed;
                edge_off_ed         <= front_edge_off_ed;
                mem_edge_ed         <= front_mem_edge_ed;
                para_valid          <= 1'b1;
            end
        end
    end

endmodule

module rd_edge_off_addr_gen #(parameter
    MEM_AWIDTH = `MEM_AWIDTH,
    PIPE_BUFFER_PTR_WIDTH = `PIPE_BUFFER_PTR_WIDTH, PIPE_AM_LEVEL = `PIPE_AM_LEVEL, PIPE_BUFFER_SIZE = `PIPE_BUFFER_SIZE,
    EDGE_OFF_ADDR_ST = `EDGE_OFF_ADDR_ST
    ) (
        input clk,
        input rst,
        input mem_full,
        input addr_rev_rd_plus_valid,

        input [MEM_AWIDTH - 1 : 0] front_edge_off_addr_ed,
        input front_para_valid,

        output buffer_empty,
        output buffer_full,
        output buffer_rev_full,
        output [MEM_AWIDTH - 1 : 0] rd_edge_off_addr,
        output rd_edge_off_valid);

    reg [MEM_AWIDTH - 1 : 0] now_edge_off_addr;
    reg [PIPE_BUFFER_PTR_WIDTH - 1 : 0] addr_rev_wr_ptr, addr_rev_rd_ptr;

    wire [PIPE_BUFFER_PTR_WIDTH - 1 : 0] gap_addr_rev_ptr;
    wire wr_rst_busy, rd_rst_busy;

    always @ (posedge clk) begin
        if (rst) begin
            now_edge_off_addr <= EDGE_OFF_ADDR_ST;
        end
        else begin
            if (!buffer_full && !wr_rst_busy && front_para_valid) begin
                if (now_edge_off_addr == front_edge_off_addr_ed) begin
                    now_edge_off_addr <= EDGE_OFF_ADDR_ST;
                end
                else begin
                    now_edge_off_addr <= now_edge_off_addr + 32 * 4;
                end
            end
        end
    end

    always @ (posedge clk) begin
        if (rst) begin
            addr_rev_wr_ptr <= 0;
            addr_rev_rd_ptr <= 0;
        end
        else begin
            if (!(mem_full || buffer_empty || buffer_rev_full)) begin
                addr_rev_wr_ptr <= addr_rev_wr_ptr + 1;
            end
            if (addr_rev_rd_plus_valid) begin
                addr_rev_rd_ptr <= addr_rev_rd_ptr + 1;
            end
        end
    end

    assign gap_addr_rev_ptr = addr_rev_wr_ptr - addr_rev_rd_ptr;
    assign buffer_rev_full = (gap_addr_rev_ptr >= PIPE_AM_LEVEL) || rst;

    addr_32bit_fifo ADDR_FIFO (
        .clk(clk), .srst(rst),
        .din(now_edge_off_addr), .wr_en(!buffer_full && !wr_rst_busy && front_para_valid), .rd_en(!(mem_full || buffer_empty || buffer_rev_full)),
        

        .dout(rd_edge_off_addr), .valid(rd_edge_off_valid),
        .empty(buffer_empty), .prog_full(buffer_full),
        .wr_rst_busy(wr_rst_busy), .rd_rst_busy(rd_rst_busy));
endmodule

module rd_edge_off_vertex_single #(parameter
    DST_ID_DWIDTH = `DST_ID_DWIDTH,
    VERTEX_PIPE_NUM = `VERTEX_PIPE_NUM
    ) (
        input clk,
        input rst,
        input [DST_ID_DWIDTH - 1 : 0] front_dst_id,
        input front_dst_data_valid,
        input any_front_dst_data_valid,
        input back_stage_vertex_full,

        output buffer_empty,
        output buffer_full,
        output [DST_ID_DWIDTH - 1 : 0] dst_id,
        output dst_data_valid);

    wire tmp_dst_data_valid_1, tmp_dst_data_valid_2;

    dst_id_fifo DI1 (
        .clk(clk), .srst(rst),
        .din(front_dst_id), .wr_en(any_front_dst_data_valid), .prog_full(buffer_full),

        .dout(dst_id), .rd_en(!back_stage_vertex_full), .empty(buffer_empty));

    valid_fifo DDV1 (
        .clk(clk), .srst(rst),
        .din(front_dst_data_valid), .wr_en(any_front_dst_data_valid),

        .dout(tmp_dst_data_valid_1), .valid(tmp_dst_data_valid_2), .rd_en(!back_stage_vertex_full));

    assign dst_data_valid = tmp_dst_data_valid_1 && tmp_dst_data_valid_2;
endmodule